; *******************************************************************************
; *										*
; * DDS_VFO_8c.asm copyright 2014 Terry Mowles VK5TM				*
; *										*
; * This code may be used for personal, non-profit use only.			*
; *										*
; * Commercial use of this code or any derivatives of it is expressly forbidden.*
; * Use in any profit making enterprise of any kind is also expressly forbidden.*
; *										*
; * This code is in part based on the works of the following:			*
; *		Curtis W. Preuss - WB2V						*
; *		Bruce Stough - AA0ED						*
; *		Craig Johnson - AA0ZZ						*
; *										*
; *******************************************************************************
;		
; *******************************************************************************
; *										*
; *	This code uses a 12F629 as a limited range VFO				*
; *	Features:								*
; *		Suitable for AD9850 or AD9851 DDS chips				*
; *		Upper and lower frequency limit					*
; *		Frequency steps of 100Hz/1kHz					*
; *		Frequency save once the encoder has stopped turning for 2Secs	*
; *		Calibrate routine to account for DDS Xtal frequency variations	*
; *										*
; *	NOTE:	Once the calibrate routine has been done, entry into		*
; *		the calibrate routine is ignored on power up. The PIC		*
; *		must be reprogrammed to perform another calibration.		*
; *										*
; *		See www.vk5tm.com for more information				*
; *										*
; *******************************************************************************
;
; *******************************************************************************
; *										*
; *	Target Controller -  PIC12F629						*
; *                    ----\/----						*
; *      V+---------- |1        8| GND						*
; *   ENCODER A---GP5 |2        7| GP0---DDS DATA				*
; *   ENCODER B---GP4 |3        6| GP1---DDS FU_UP				*
; *   CAL---------GP3 |4        5| GP2---DDS W_CLOCK				*
; *                    ----------						*
; *******************************************************************************

; !!----- SELECT ONE OF THESE DDS TYPES AND TURN THE OTHER OFF -----!!

#define AD9850		; Using the AD9850
;#define AD9851		; Using the AD9851

; *******************************************************************************
; *	Device type and options.						*
; *******************************************************************************
;
        processor 12F629
        radix     dec
	errorlevel -302  ; Skip out of bank nuisance messages

; *******************************************************************************
; *	Configuration fuse information for 12F629				*
; *******************************************************************************

        include   <P12F629.INC> 

 __config _CP_OFF&_CPD_OFF&_BODEN_OFF&_MCLRE_OFF&_PWRTE_ON&_WDT_OFF&_FOSC_INTRCIO  
;                                                                             
; *******************************************************************************
; * 	General equates.  These may be changed to accommodate the reference 	* 
; *	clock frequency, the desired upper frequency limit, the desired lower 	*
; *	frequency limit, and the default startup frequency.			*
; *******************************************************************************
;
; ref_osc represents the change in the frequency control word which results 
; in a 1 Hz change in output frequency.  It is interpreted as a fixed point
; integer in the format <ref_osc_3>.<ref_osc_2><ref_osc_1><ref_osc_0>  
;
; The values for common oscillator frequencies are as follows:
;
; Frequency    ref_osc_3    ref_osc_2    ref_osc_1    ref_osc_0
;
; 180.00 MHz     0x17         0xDC         0x65         0xDF
; 125.00 MHz     0x22         0x5C         0x17         0xD0
; 120.00 MHz     0x23         0xCA         0x98         0xCE                  
; 100.00 MHz     0x2A         0xF3         0x1D         0xC4                  
;  90.70 MHz     0x2F         0x5A         0x82         0x7A                  
;  66.66 MHz     0x40         0x6E         0x52         0xE7                  
;  66.00 MHz     0x41         0x13         0x44         0x5F                  
;  50.00 MHz     0x55         0xE6         0x3B         0x88                  
;  30.00 MHz     0x8F         0x2A         0x63         0x39                  
;
; To calculate other values: 
;	ref_osc_3 = (2^32 / oscillator_freq_in_Hertz).                           
;	ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of           
;	(2^32 / oscillator_freq_in_Hertz) times 2^24.                           
;	Note:   2^32 = 4294967296 and 2^24 = 16777216
;
; For example, for a 120 MHz clock:
;	ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23)  
;	ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32         
;	13277390.32 = 0xCA98CE, so ref_osc_2 is CA.                           
;	ref_osc_1 is the next byte of 0xCA98CE, or 98
;	ref_osc_0 is the last byte of 0xCA98CE, or CE
;
; For example, for a 180 MHz clock:
;	ref_osc_3 is (2^32 / 180 x 10^6) = 23.860929422 truncated to 23 (0x17)  
;	ref_osc_2 is the high byte of (.860929422 x 2^24) = 14443998
;	14443998 = 0xDC65DE, so high byte is DC.                           
;	ref_osc_1 is the next byte of 0xDC65DE, or 65
;	ref_osc_0 is the last byte of 0xDC65DE, or DE
;
#ifdef AD9850
;	For 125 MHz Oscillator =======
ref_osc_3   equ 0x22		; Most significant osc byte
ref_osc_2   equ 0x5C		; Next byte
ref_osc_1   equ 0x17		; Next byte
ref_osc_0   equ 0xD0		; Least significant byte
#endif
#ifdef AD9851
;	For 180 MHz (30 MHz clock and 6x multiplier)                               
ref_osc_3   equ 0x17		; Most significant osc byte                
ref_osc_2   equ 0xDC		; Next byte                                
ref_osc_1   equ 0x65		; Next byte                                
ref_osc_0   equ 0xDE		; Least significant byte                   
#endif

; *******************************************************************************
;	Change limits to suit
;
;	Limit_0...3 contains the upper limit frequency as a 32 bit integer.
;
limit_3   equ 0x00		; Most significant byte for 5.5 MHz    
limit_2   equ 0x53		; Next byte
limit_1   equ 0xEC		; Next byte
limit_0   equ 0x60		; Least significant byte
;
;	Low_limit_3..0 contains the lower frequency limit as a 32 bit integer
;
limit_low_3 equ 0x00		; Most significant byte for 5.0 MHz
limit_low_2 equ 0x4C		; Next byte
limit_low_1 equ 0x4B		; Next byte
limit_low_0 equ 0x40		; Least significant byte 
;
;	Default contains the default startup frequency as a 32 bit integer.
;
default_3 equ 0x00		; Most significant byte for 5.0 MHz
default_2 equ 0x4C		; Next byte
default_1 equ 0x4B		; Next byte
default_0 equ 0x40		; Least significant byte
;
;	Frequency at which Calibration will take place
;
cal_freq_3 equ 0x00		; Most significant byte for 5.250 MHz
cal_freq_2 equ 0x50		; Next byte
cal_freq_1 equ 0x1B		; Next byte
cal_freq_0 equ 0xD0		; Least significant byte 
;
; *******************************************************************************
;
; *******************************************************************************
; *	Setup the initial constant, based on the frequency of the reference	*
; *	oscillator.  This can be tweaked with the calibrate function.		*
; *	Stored in EEPROM							*
; *******************************************************************************
; 
        ORG     0x2100
;	ref_osc bytes must be first 4 bytes of EEPROM                               
        DATA    ref_osc_0
        DATA    ref_osc_1
        DATA    ref_osc_2
        DATA    ref_osc_3

;	startup frequency bytes must be next 4 bytes of EEPROM                      
        DATA    default_0  ; startup -> freq_0                                
        DATA    default_1  ; startup -> freq_1                                
        DATA    default_2  ; startup -> freq_2                                
        DATA    default_3  ; startup -> freq_3 
	DATA	0,0,0,0,0,0,0                               
;
; *******************************************************************************
; *	Assign names to IO pins.						*
; *******************************************************************************
;
#DEFINE DDS_clk		GPIO,2	; AD9850/AD9851 write clock
#DEFINE DDS_dat		GPIO,0	; AD9850/AD9851 serial data input
#DEFINE DDS_load	GPIO,1	; Update pin on AD9850/AD9851

; ------------Interupt defines---------------------------------------------------
;
w_temp        EQU     0x5E	; variable used for context saving 
status_temp   EQU     0x5F	; variable used for context saving     
;
; *******************************************************************************
; *	Allocate variables in general purpose register space			*
; *******************************************************************************
;
	CBLOCK	0x20		; Start Data Block

	AD9851_0		; AD9850/AD9851 control word                
	AD9851_1		;  (5 bytes)                                
	AD9851_2                                                            
	AD9851_3                                                            
	AD9851_4                                                            
	fstep_0			; Frequency inc/dec 
	fstep_1			;  (4 bytes)
	fstep_2
	fstep_3
	low_limit_3		; Low frequency limit
	low_limit_2		; (4 bytes)
	low_limit_1
	low_limit_0
	mult_count		; Used in calc_dds_word 
	bit_count		;   "
	byte2send		;
	osc_temp_0		; Oscillator frequency 
	osc_temp_1		;  (4 bytes)
	osc_temp_2        
	osc_temp_3
	timer1			; Used in delay routines
	timer2			;   "
	ren_new			; New value of encoder pins A and B
	ren_old			; Old value of encoder pins A and B
	ren_read		; Encoder pins A and B and switch pin
	last_dir		; Indicates last direction of encoder
	next_dir		; Indicates expected direction
	enc_counter		; Divide by 4 counter for mechanical encoder
	saved			; Flags for frequency save routine
				; saved,0 used in read encoder routine to jump flag for frequency save
				; =1 calibrate mode active
				; =0 calibrate mode not active

				; saved,1 test if frequency save done
				; =1 frequency save done
				; =0 frequency save not done

				; saved,2 used in interupt routine
	CNT1			; Counter for interrupt routine                       
	freq_0			; Frequency (hex)
	freq_1			; (4 bytes)			
	freq_2			;  
	freq_3			;   
	osc_0			; Current oscillator 
	osc_1			; (4 bytes)
	osc_2
	osc_3
	cal_flag		; Flag to test if CAL done. Bit 0 = 1 if done
	ENDC			; End of Data Block

; *******************************************************************************
; *	The 12F629 resets to 0x00.						*
; *	The Interrupt vector is at 0x04.					*
; *******************************************************************************
;
	ORG     0x0000                
	goto    start		; Jump to main program

	ORG     0x0004		; interrupt routine for approx 2 second counter
	movwf   w_temp		; save off current W register contents
	movf	STATUS,w	; move status register into W register
	movwf	status_temp	; save off contents of STATUS register
	decfsz 	CNT1,F 		; Decrease CNT1. If zero, skip next instruction
	goto	not_yet 	; Not zero goto not yet
	bsf	saved,2		; set interrupt timed out flag
	goto	exit_interrupt	;
not_yet
	bcf INTCON,T0IF		; Clear Timer0 interrupt flag, otherwise interrupt will occur again
	bsf INTCON,T0IE		; Enable Timer0 interrupt again
	movlw 39
	movwf TMR0 		; Reload Timer0
;
exit_interrupt
;
	movf    status_temp,w	; retrieve copy of STATUS register
	movwf	STATUS		; restore pre-isr STATUS register contents
	swapf   w_temp,f
	swapf   w_temp,w	; restore pre-isr W register contents
	retfie 			; Enable general interrupts and return
;
; *******************************************************************************
; *										*
; * Purpose:  This is the start of the program.  It detects whether to enter	*
; *           calibrate mode.  If so, it calls the Calibrate routine.  		*
; *           Otherwise, it sets the power-on frequency	and enters the loop to 	*
; *           poll the encoder.							*
; *										*
; *   Input:  The start up frequency is defined in the default_3...0		*
; *           definitions above, and relies on the reference oscillator		*
; *           constant defined in ref_osc_3...ref_osc_0.			*
; *										*
; *  Output:  Normal VFO operation.						*
; *										*
; *******************************************************************************
;
start
	movlw	0x07		; Code to turn off the analog comparitors 
	movwf	CMCON		; 
	bsf	STATUS,RP0	; Switch to bank 1
	call	0x3FF		; retrieve factory calibration value
	movwf	OSCCAL		; update register with factory cal value 
	movlw	b'00111000'	; GPIO 3,4,5 inputs 0,1,2 outputs     
	movwf	TRISIO		; 
	bcf	STATUS,RP0	; Switch back to bank 0
	clrf	cal_flag	;
;
;	Initialize DDS Module with zero freq
;
	clrf	AD9851_0	; AD9850/51 control word 
	clrf	AD9851_1	; (5 bytes)
	clrf	AD9851_2
	clrf	AD9851_3
	clrf	AD9851_4
	call	send_dds_word	; Send it to the DDS
	call	send_dds_word	; twice to be sure
;
	movlw	limit_low_3	; Most significant byte for lower limit
	movwf	low_limit_3
	movlw	limit_low_2	; Next byte
	movwf	low_limit_2
	movlw	limit_low_1	; Next byte
	movwf	low_limit_1
	movlw	limit_low_0	; Least significant byte 
	movwf	low_limit_0
;
;       Enter Calibrate Mode if GPIO,3 is low when turning the power on and CAL not already done.
;	Test flag to see if CAL already done. Jump over this section if done. 
	movlw	8		; 
	bsf	STATUS,RP0	; Switch to bank 1
	movwf	EEADR		; Point to flag location in EEprom
	call	read_EEPROM	; Read EEprom at address 4
	bcf	STATUS,RP0	; Switch to bank 0
	movwf	cal_flag	; Move data to register
	btfsc	cal_flag,0	; Test set_flag,0 - will be 0 if CAL not done
	goto	read_EEocs	; set_flag,0 is 1 so CAL already done
	btfss	GPIO,3		; Cal pushbutton pressed
	call	calibrate	; Yes,call calibration routine
;       
;       Get the reference oscillator constant from the EEPROM.
;
read_EEocs
;
	bsf	STATUS,RP0	; Switch to bank 1
	clrf	EEADR		; Reset the EEPROM read address to 0
	call	read_EEPROM	; Read EEPROM (all in bank 1)
	movf	EEDATA,w	; Get the first osc byte
	movwf	osc_0		; Save osc frequency
	call	read_EEPROM	; Read EEPROM
	movf	EEDATA,w	; 
	movwf	osc_1		; Save it
	call	read_EEPROM	; Read EEPROM
	movf	EEDATA,w	; 
	movwf	osc_2		; Save it
	call	read_EEPROM	; Read EEPROM
	movf	EEDATA,w	;
	movwf	osc_3		; Save it
;
;       Set the power on frequency to the defined value.
;       (They always follow the osc bytes)                                     
;
	call	read_EEPROM	; Read EEPROM                                
	movf	EEDATA,w	; Get the first default freq byte            
	movwf	freq_0		; Save it
	call	read_EEPROM	; Read EEPROM                                
	movf	EEDATA,w	; Get the next freq byte                     
	movwf	freq_1		; Save it
	call	read_EEPROM	; Read EEPROM                                
	movf	EEDATA,w	; Get the next freq byte                     
	movwf	freq_2		; Save it                                    
	call	read_EEPROM	; Read EEPROM                                 
	movf	EEDATA,w	; Get the last freq byte                     
	movwf	freq_3		; Save it
	bcf	STATUS,RP0	; Back to bank 0 
;
;	Send power on frequency to the DDS chip.
;
	call	calc_dds_word	; Convert to delta value
	call	send_dds_word	; Send the power-on frequency to the
				; AD9850/AD9851 in serial mode
;
;	Get the power on encoder value.
;
	movf    GPIO,w
	movwf   ren_old		; Save it in ren_old
	movlw	b'00110000'	; Get encoder mask (GPIO,4 and GPIO,5)
	andwf   ren_old,f	; Get encoder bits and zero all other bits
;
	clrf	saved		; 
	clrf	last_dir	; Clear the knob direction indicator
;
;	Setup interupt on change pins
	bsf	STATUS,RP0	; Switch to bank 1
	movlw	b'00110000'	; Set GPIO,4 & 5 (encoder input) to interupt on change
	movwf	IOC		;
	bcf	STATUS,RP0	; Back to bank 0 
;
; Fall into the Main Program Loop
;
; *******************************************************************************
; *										*
; * Purpose:	This is the Main Program Loop. The program's main loop		*
; *		calls poll_encoder, which continuously polls the rotary shaft	*
; *		encoder.  When the shaft encoder has changed, the direction	*
; *		it moved is determined and stored in last_dir.  The subroutine	*
; *		then returns to main.						*                       
; *										*
; *		The variable fstep is added or subtracted from the current	*
; *		VFO frequency and stored in freq.				*
; *		Next, the subroutine calc_dds_word is used to calculate the DDS	*
; *		frequency control word from the values in freq and osc.		*
; *		The result is stored in AD9850/AD9851. This data is transferred	*
; *		to the AD9851 DDS chip by calling the subroutine send_dds_word.	*
; *										*
; *		The frequency is saved to EPROM 2 seconds after the encoder	*
; *		stops turning.							*
; *										*
; *   Input:	None.								*
; *										*
; *  Output:	None.								*
; *										*
; *******************************************************************************
;
;	Put PIC to sleep until encoder moved for the first time
	call	nod_off
main
				; Start of 2s timer interrupt routine
	bsf	STATUS,RP0	; Switch BANK1
	movlw	b'00000111'	; Set TMRO prescaler 1:256
	movwf	OPTION_REG	; 
	bcf	STATUS,RP0	; Switch to BANK0
;
	movlw	b'00000100'
	movwf	INTCON		; Enable Timer0 interrupt, Disable all others
	bsf	INTCON,GIE	; Enable general interrupts
	bsf	INTCON,T0IE	; Enable Timer0 interrupt
;
	movlw	39
	movwf	TMR0		; Timer0 starting point, writing this will start timer after two ins. cyc.
	movlw	18		; Overflow counter for 2 second
	movwf	CNT1
;
poll
	call	poll_encoder	; Check for knob movement (wait there!)      
				; Return here when encoder change detected
;
step				; Step size 100Hz/1kHz
	clrf	fstep_3		; can be changed to 10Hz step
	clrf	fstep_2		; by changing value as below
	clrf	fstep_1		; 

	btfsc	GPIO,3		; is GPIO,3 low
	goto	step100		; no - skip to 100Hz step size
	movlw	0xE8		; Yes - load values for 1kHz step
	movwf	fstep_0		;
	movlw	0x03		;
	movwf	fstep_1		;
	goto	stepdir		; Goto direction test
;
step100
;	movlw	0x0A		; 10Hz steps
	movlw	0x64		; 100 Hz steps by setting fstep,0 to 100 (hex 64)
	movwf	fstep_0		; 
;
;	Based on the knob direction, either add or subtract the increment, 
;	then update DDS.
; 
stepdir
	btfsc	last_dir,0	; Is the knob going up?
	goto	up		; Yes, then add the increment
down
	call	sub_step	; Subtract fstep from freq
	goto	update		; Update DDS                         
up
	call	add_step	; Add fstep to freq
	call	check_add	; Make sure we did not exceed the maximum
update 
	call	calc_dds_word	; Calculate the control word for the DDS chip
	call	send_dds_word	; Send the control word to the DDS chip
;
	goto	main		; Continue main loop
;
; *******************************************************************************
; *										*
; * Purpose:	This routine adds the 32 bit value of fstep to the 32 bit	*
; *		value in freq.  When incrementing, the fstep value is a		*
; *		positive integer.  When decrementing, fstep is the complement	*
; *		of the value being subtracted.					*
; *										*
; *   Input:	The 32 bit values in fstep and freq				*
; *										*
; *  Output:	The sum of fstep and freq is stored in freq.  When incrementing	*
; *		this value may exceed the maximum.  When decrementing, it may   *
; *		go negative.                                                    *
; * 										*
; *******************************************************************************
;
add_step
	movf	fstep_0,w	; Get low byte of the increment
	addwf	freq_0,f	; Add it to the low byte of freq
	btfss	STATUS,C	; Any carry?
	goto	add1		; No, add next byte
	incfsz	freq_1,f	; Ripple carry up to the next byte
	goto	add1		; No new carry, add next byte
	incfsz	freq_2,f	; Ripple carry up to the next byte
	goto	add1		; No new carry, add next byte
	incf	freq_3,f	; Ripple carry up to the highest byte
add1
	movf	fstep_1,w	; Get the next increment byte
	addwf	freq_1,f	; Add it to the next higher byte
	btfss	STATUS,C	; Any carry?
	goto	add2		; No, add next byte
	incfsz	freq_2,f	; Ripple carry up to the next byte
	goto	add2		; No new carry, add next byte
	incf	freq_3,f	; Ripple carry up to the highest byte
add2
	movf	fstep_2,w	; Get the next to most significant increment
	addwf	freq_2,f	; Add it to the freq byte
	btfss	STATUS,C	; Any carry?
	goto	add3		; No, add last byte
	incf	freq_3,f	; Ripple carry up to the highest byte
add3
	movf	fstep_3,w	; Get the most significant increment byte
	addwf	freq_3,f	; Add it to the most significant freq
	return			; Return to the caller
;
; *******************************************************************************
; *										*
; * Purpose:	Check if freq exceeds the upper limit.				*
; *										*
; *   Input:	The 32 bit values in freq					*
; *										*
; *  Output:	If freq is below the limit, it is unchanged.  Otherwise, it is	*
; *		set to equal the upper limit.					*
; *										*
; *******************************************************************************
;
check_add
;
;	Check the most significant byte.
;
	movlw	0xFF-limit_3	; Get (FF - limit of high byte)
	addwf	freq_3,w	; Add it to the current high byte
	btfsc	STATUS,C	; Was high byte too large?
	goto	set_max		; Yes, apply limit
	movlw	limit_3		; Get high limit value
	subwf	freq_3,w	; Subtract the limit value
	btfss	STATUS,C	; Are we at the limit for the byte?
	goto	exit1		; No, below.  Checks are done.
;
;	Check the second most significant byte.
;
	movlw	0xFF-limit_2	; Get (FF - limit of next byte)
	addwf	freq_2,w	; Add it to the current byte
	btfsc	STATUS,C	; Is the current value too high?
	goto	set_max		; Yes, apply the limit
	movlw	limit_2		; Second limit byte
	subwf	freq_2,w	; Subtract limit value
	btfss	STATUS,C	; Are we at the limit for the byte?
	goto	exit1		; No, below.  Checks are done.
;
;	Check the third most significant byte.
; 
	movlw	0xFF-limit_1	; Get (FF - limit of next byte)
	addwf	freq_1,w	; Add it to the current byte
	btfsc	STATUS,C	; Is the current value too high?
	goto	set_max		; Yes, apply the limit
	movlw	limit_1		; Third limit byte
	subwf	freq_1,w	; Subtract limit value
	btfss	STATUS,C	; Are we at the limit for the byte?
	goto	exit1		; No, below.  Checks are done.
; 
;	Check the least significant byte.
;
	movlw	limit_0		; Fourth limit byte
	subwf	freq_0,w	; Subtract limit value
	btfss	STATUS,C	; Are we at the limit for the byte?
	goto	exit1		; No, below.  Checks are done.
set_max
	movlw	limit_0		; Get least significant limit
	movwf	freq_0		; Set it in freq
	movlw	limit_1		; Get the next byte limit
	movwf	freq_1		; Set it in freq_1
	movlw	limit_2		; Get the next byte limit
	movwf	freq_2		; Set it in freq_2
	movlw	limit_3		; Get the most significant limit
	movwf	freq_3		; Set it in freq_3
exit1
	return			; Return to the caller
;
; *******************************************************************************
; *										*
; * Function:	sub_step							*
; *										*
; * Purpose:	Subtract the increment step from freq.				*
; *										*
; *   Input:	The values in fstep and freq_3..0.				*
; *										*
; *  Output:	None								*
; *										*
; * Revisions:	Modified for limited range VFO. 29-9-13 VK5TM			*
; *										*
; *******************************************************************************
;
sub_step
;
	call	invert_fstep	; Invert fstep_3..0 to perform the subtraction
	call	add_step	; Add the complement to do the subtraction
;
; *******************************************************************************
; *										*
; * Function:	low_limit_chk							*
; *										*
; * Purpose:	Test the new frequency to see if it is above the lower band	*
; *		limit. If not, the frequency is set to the band lower limit.	*
; *										*
; * Input:	Freq_0...3 and Low_limit_0...3					*
; *										*
; * Output:	Original frequency if above low limit or low_limit_0..3 in	*
; *		Freq_0...3							*
; *										*
; *******************************************************************************
;
low_limit_chk
;       Check the most significant byte.
;
	btfsc	freq_3,7
	goto	set_low           ; Yes, set to lower frequency limit
	movf	freq_3,w
	subwf	low_limit_3,w
	btfss 	STATUS,C          ; Are we at the limit for the byte?
	goto	limit_exit        ; No, above. 
	btfss	STATUS,Z          ; Are the bytes equal?
	goto	set_low           ; No, so vfo_X_3 > limit_3.
;
;       Check the second most significant byte when MSB equals limit_3
;
	movf	freq_2,w
	subwf	low_limit_2,w
	btfss	STATUS,C          ; Are we at the limit for the byte?
	goto	limit_exit        ; No, above.  Check next.
	btfss	STATUS,Z          ; Might they be equal?
	goto	set_low           ; Nope, so vfo_X_2 > limit_2
;
;       Check the third most significant byte.
;
	movf	freq_1,w
	subwf	low_limit_1,w
	btfss	STATUS,C          ; Are we at the limit for the byte?
	goto	limit_exit        ; No, above.  Checks are done.
	btfss	STATUS,Z          ; Check if the bytes are equal
	goto	set_low           ; No, so vfo_X_1 > limit_1
; 
;       Check the least significant byte.
;
	movf	freq_0,w
	subwf	low_limit_0,w
	btfss	STATUS,C          ; Are we at the limit for the byte?
	goto	limit_exit        ; No, above.  Checks are done.      
;
;	The frequency is below the band lower limit. Set frequency to the
;	band starting frequency.        
set_low
;
	movf	low_limit_0,w
	movwf	freq_0
	movf	low_limit_1,w
	movwf	freq_1
	movf	low_limit_2,w
	movwf	freq_2
	movf	low_limit_3,w
	movwf	freq_3
;
limit_exit
	call	invert_fstep	; Put fstep back to original value
	return			; Return to caller
;
; *******************************************************************************
; *										*
; * Function:	invert_fstep							*
; *										*
; *  Purpose:	Support function for sub_step and calibrate. This function	*
; *		negates the value of fstep_3..0 to assist in the frequency	*
; *		decrement. This operation is performed twice by sub_step, and	*
; *		is also used by calibrate.					*
; *										*
; *    Input:	fstep_3, fstep_2, fstep_1, fstep_0				*
; *										*
; *   Output:	fstep_3..0 contain the 2's complement of their original value	*
; *										*
; *******************************************************************************
;
invert_fstep
				; Invert the bits in
	comf	fstep_0,f	; fstep_0
	comf	fstep_1,f	; fstep_1
	comf	fstep_2,f	; fstep_2
	comf	fstep_3,f	; fstep_3
	incfsz	fstep_0,f	; Now incremnt fstep_0 to get 2's complement
	goto	invert_done	; If fstep_0 > 0, then done
				; Else, there was a carry out of fstep_0
	incfsz	fstep_1,f	; Add 1 to fstep_1
	goto	invert_done	; If fstep_1 > 0, then done
				; Else, there was a carry out of fstep_1
	incfsz	fstep_2,f	; Add 1 to fstep_2
	goto	invert_done	; if fstep_2 > 0, then done
				; Else, there was a carry out of fstep_2
	incf	fstep_3,f	; Increment the high byte
;
invert_done
	return			; Back to caller
;
; *******************************************************************************
; *										*
; * Purpose:	This routine does the following:				*
; *										*
; *		Reads the encoder bits until a change is detected, then		*
; *		determines the direction the knob was moved.			*
; *										*
; *   Input:	Knob input read from GPIO					*
; *		ren_old -> the last encoder bits read                           *
; *		last_dir -> the last direction moved                            *
; *										*
; *  Output:	ren_new -> the current encoder bits				*
; *		last_dir -> the last direction (0 = down, 1 = up)		*
; *										*
; *******************************************************************************
;
poll_encoder
;
	btfsc	saved,0		; Test if in calibrate mode - ignore interrupt flags
	goto	read_encoder
	btfsc	saved,2		; Test interrupt flag, jump over if not set
	call	update_EEPROM	; Call to save freq when changed and timer timed out
;
read_encoder
;
	movf	GPIO,w
	movwf	ren_read	; Save it
;
;!!!--- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!!
	call	wait_1ms	; debounce time
	movf	GPIO,w
	xorwf	ren_read,w	; Compare with previous value
	btfss	STATUS,Z	; Are they equal?
	goto	read_encoder	; Poll again if not 
;!!! ------------------------------------------------------------- !!!
;
	movlw	b'00110000'
	andwf	ren_read,w	; Isolate encoder bits into W               
	movwf	ren_new		; Save new value
	xorwf	ren_old,w	; Has it changed?
	btfsc	STATUS,Z	; Check zero-flag (zero if no change)        
	goto	poll_encoder	; No change, keep looking until it changes  
				; Zero-flag is not set, so continue on
;       
; It changed. Now determine which direction the encoder turned.
;============================================================================= 
; Encoder bits are on GPIO,4 and GPIO,5 - the bits 4 & 5 of ren_new
; A and B are "gray code" - 90 degrees out of phase (quadrature)
;         ___     ___                                                          
;        |   |   |   |                                                         
; A  ____|   |___|   |___                                                      
;           ___     ___                                                        
;          |   |   |   |                                                       
; B     ___|   |___|   |___                                                    
;       ^ ^ ^ ^ ^ ^ ^ ^                                                        
;       a b c d a b c d                                                        
;                                                                              
;              A B                                                             
; At point a:  0 0                                                             
; At point b:  1 0                                                             
; At point c:  1 1                                                             
; At point d:  0 1                                                             
;                                                                              
; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is:          
;     00, 10, 11, 01, 00, 10, 11, 01, etc.                                     
;                                                                              
; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is:        
;     01, 11, 10, 00, 01, 11, 10, 00, etc.                                     
;                                                                              
; To determine if the sequence is UP or DOWN:                                  
;   1) Take the "Right-Bit" of any pair.                                       
;   2) XOR it with the "Left-Bit" of the next pair in the sequence.            
;   3) If the result is 1 it is UP                                             
;      If the result is 0 it is DOWN                                           
;                                                                              
; The direction flag is 0 (DOWN) or 1 (UP)
;============================================================================= 
;
	bcf	STATUS,C	; Clear the carry bit to prepare for rotate  
	rlf	ren_old,f	; Rotate old bits left to align "Right-Bit"  
	movf	ren_new,w	; Set up new bits in W                       
	xorwf	ren_old,f	; XOR old (left shifted) with new bits       
	movf	ren_old,w	; Put XOR results into W also                
	clrf	last_dir	; Clear last_dir (default is DN)             
	btfsc	ren_old,5                          
	goto	up2		; No, exit3 
;
;!!! --- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!!
	movf	ren_new,w	; Get the current encoder bits
	movwf	ren_old		; Save them in ren_old for the next time
	decf	enc_counter,f	; decrement the inter-detent counter
	btfsc	enc_counter,0	; Check if bit 0 is cleared
	goto	poll_encoder	; If not, then poll some more
	btfsc	enc_counter,1	; Else, check if bit 1 is cleared
	goto	poll_encoder	; If not, then poll some more
				; Else, assume the encoder is on a detent
;!!! -------------------------------------------------------------- !!!
	goto	exit3
;                                 
up2 
	bsf	last_dir,0	; Set "UP" bit.
;
;!!! --- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!!
	movf	ren_new,w	; Get the current encoder bits
	movwf	ren_old		; Save them in ren_old for the next time
	incf	enc_counter,f	; Increment the encoder counter
	btfsc	enc_counter,0	; Return only on modulo 4 counts
	goto	poll_encoder	;
	btfsc	enc_counter,1
	goto	poll_encoder	; Loop again if not modulo 4
;!!! -------------------------------------------------------------- !!!
;                   
exit3 
	movf	ren_new,w	; Get the current encoder bits
	movwf	ren_old		; Save them in ren_old for the next time
	return 			; Return to the caller
;                                                                              
; *******************************************************************************
; *										*
; * Purpose:	This routine is entered at start up if the calibrate pads are	*
; *		shorted at power-on time.					*
; *										*
; * 		The DDS chip is programmed to produce a frequency, based on the	*
; *		osc value stored in the EEPROM and the calibration frequency as	*
; *		at the head of the code. As long as the pads are shorted, the	*
; *		osc value is slowly altered to allow the output	to be trimmed. 	*
; *		Once the encoder is turned after the short is removed from the 	*
; *		Cal pads, the new osc value is stored in the EEPROM and normal	*
; *		operation begins.						*
; *										*
; *   Input:	The original osc constant in EEPROM				*
; *  										*
; *  Output:	The corrected osc constant in EEPROM				*
; *										*
; *******************************************************************************
;
calibrate
	bsf	saved,0		; set flag for poll encoder routine
	bcf	INTCON,GIE	; Turn off interupts to be sure
;
	movlw	cal_freq_0	; Move Calibration frequency constants
	movwf	freq_0		; to freq_0...3 for Calibration routine.
	movlw	cal_freq_1	; 
	movwf	freq_1		;    
 	movlw	cal_freq_2	;
	movwf	freq_2		;   
	movlw	cal_freq_3	;
	movwf	freq_3		;    
;
;	Read the starting reference oscillator value from EEPROM.
;
	bsf	STATUS,RP0	; Switch to bank 1
	clrf	EEADR		; Reset the EEPROM read address
	call	read_EEPROM	; Read first byte from EEPROM (all in bank 1)
	movf	EEDATA,w	; Get the first osc byte
	movwf	osc_0		; Save osc frequency
	call	read_EEPROM	; Read second byte from EEPROM
	movf	EEDATA,w	; 
	movwf	osc_1		; Save it
	call	read_EEPROM	; Read third byte from EEPROM
	movf	EEDATA,w	; 
	movwf	osc_2		; Save it
	call	read_EEPROM	; Read fourth byte from EEPROM
	movf	EEDATA,w	;
	movwf	osc_3		; Save it
	bcf	STATUS,RP0	; Back to bank 0 for store
;
cal_loop
	call	calc_dds_word	; Calculate DDS value based on current osc
	call	send_dds_word	; Update the DDS chip
	call	poll_encoder	; Wait until the encoder has moved.
	btfsc	GPIO,3		; Calibrate switch/jumper still set?
	goto	cal_out		; No, go to exit and save values to EEPROM
	clrf	fstep_3		; Clear the three most significant
	clrf	fstep_2		; bytes of fstep
	clrf	fstep_1		; 
	movlw	0x10		; 
	movwf	fstep_0		; Use small increment
	nop			; Wait a cycle
	btfsc	last_dir,0	; Are we moving down?
	goto	faster		; No, increase the osc value
;
;	slower
;
	comf	fstep_0,f	; Subtraction of fstep is done by
	comf	fstep_1,f	; adding the twos compliment of fsetp
	comf	fstep_2,f	; to osc
	comf	fstep_3,f	; 
	incfsz	fstep_0,f	; Increment last byte
	goto	faster		; Non-zero, continue
	incfsz	fstep_1,f	; Increment next byte
	goto	faster		; Non-zero, continue
	incfsz	fstep_2,f	; Increment next byte
	goto	faster		; Non-zero, continue
	incf	fstep_3,f	; Increment the high byte
faster
	movf	fstep_0,w	; Get the low byte increment
	addwf	osc_0,f		; Add it to the low osc byte
	btfss	STATUS,C	; Was there a carry?
	goto	add4		; No, add the next bytes
	incfsz	osc_1,f		; Ripple carry up to the next byte
	goto	add4		; No new carry, add the next bytes
	incfsz	osc_2,f		; Ripple carry up to the next byte
	goto	add4		; No new carry, add the next bytes
	incf	osc_3,f		; Ripple carry up to the highest byte
add4
	movf	fstep_1,w	; Get the second byte increment
	addwf	osc_1,f		; Add it to the second osc byte
	btfss	STATUS,C	; Was there a carry?
	goto	add5		; No, add the third bytes
	incfsz	osc_2,f		; Ripple carry up to the next byte
	goto	add5		; No new carry, add the third bytes
	incf	osc_3,f		; Ripple carry up to the highest byte
add5
	movf	fstep_2,w	; Get the third byte increment
	addwf	osc_2,f		; Add it to the third osc byte
	btfss	STATUS,C	; Was there a carry?
	goto	add6		; No, add the fourth bytes
	incf	osc_3,f		; Ripple carry up to the highest byte
add6
	movf	fstep_3,w	; Get the fourth byte increment
	addwf	osc_3,f		; Add it to the fourth byte
	goto	cal_loop	; Yes, stay in calibrate mode
;
;	Write final value to EPROM
;
cal_out
	bsf	STATUS,RP0	; Switch to bank 1
	movf	osc_0,w		; Get the first osc byte to record
	clrf	EEADR		; osc bytes start at EEPROM address 0
	movwf	EEDATA		; Put byte in EEPROM write location 
	call	write_EEPROM	; 
	movf	osc_1,w		; Get the second byte to record
	movwf	EEDATA		; Put byte in EEPROM write location 
	call	write_EEPROM	;
	movf	osc_2,w		; Get the third byte to record
	movwf	EEDATA		; Put byte in EEPROM write location 
	call	write_EEPROM	;
	movf	osc_3,w		; Get the fourth byte to record
	movwf	EEDATA		; Put byte in EEPROM write location 
	call	write_EEPROM	;
	movlw	8
	movwf	EEADR		; Move to EEPROM write location
	bsf	cal_flag,0	; Set bit 0 of cal_flag => CAL done
	movfw	cal_flag	; Move file to w for save to EEprom		
	movwf	EEDATA		; Put byte in EEprom write location 
	call	write_EEPROM	;
	bcf	STATUS,RP0	; Back to bank 0 
	bcf	saved,0		; clear flag used poll encoder routine
	return			; Return to the caller
;                      
; *******************************************************************************
; *										*
; * Purpose:	This routine will save the current frequency in EEPROM. This	*
; *		frequency will then be used as the initial frequency upon start	*
; *		up. Frequency is automatically saved 2 seconds after encoder	*
; *		stops moving							*
; *										*
; *   Input:	The constants in freq_0...3					*
; *										*
; *  Output:	None								*
; *										*
; *******************************************************************************
; 
update_EEPROM
	bcf	INTCON,GIE	; turn interrupts off
	bcf	saved,2
	bsf	STATUS,RP0	; Switch to bank 1
	movlw	4		; Default startup frequency address location
	movwf	EEADR		; and set up for start of EEPROM writes    
	movf	freq_0,w	; Get the first freq byte to write
	movwf	EEDATA		; First freq byte to EEPROM Write register
	call	write_EEPROM	; Write it
	movf	freq_1,w	; Get the second freq byte to write
	movwf	EEDATA		; Second freq byte to EEPROM Write register
	call	write_EEPROM	; Write it
	movf	freq_2,w	; Get the third freq byte to write
	movwf	EEDATA		; Third freq byte to EEPROM Write register
	call	write_EEPROM	; Write it
	movf	freq_3,w	; Get the fourth freq byte to write
	movwf	EEDATA		; Fourth freq byte to EEPROM Write register
	call	write_EEPROM	; Write it
	bcf	STATUS,RP0	; Back to bank 0
	call	nod_off		; Go to sleep until encoder moved
	return			;   
;
; *******************************************************************************
; *										*
; *	Sleep routine. 								*
; *	Puts PIC to sleep and waits for encoder to move.		 	*
; *										*
; *										*
; *******************************************************************************
;
nod_off
	bsf	INTCON,GPIE	; Enable Port Change Interupt
	movf	GPIO,w		; clear the change condition (see 12F629 data sheet)
	bcf	INTCON,GPIF	; clear the interrupt flag
	sleep			; Night,night. Sweet dreams.
	nop			; Wake from sleep (may still be a bit drowsy)
	return			; Back to work
                                         
;
; *******************************************************************************
; *										*
; *		Required sequence to write to EEPROM				*
; *		Used by update_EEPROM and calibrate routines			*
; *										*
; *******************************************************************************
;
write_EEPROM
	bsf	EECON1,WREN	; Set the EEPROM write enable bit
        ; Start of required sequence
	bcf	INTCON,GIE	; Disable interrupts
	movlw	0x55		; Write 0x55 and 0xAA to EECON2
	movwf	EECON2		; control register, as required
	movlw	0xAA		;   
	movwf	EECON2		;
	bsf	EECON1,WR	; Set WR bit to begin write
        ; End of required sequence
bit_check
	btfsc	EECON1,WR	; Has the write completed?
	goto	bit_check	; No, keep checking
	bsf	EECON1,GIE	; Enable  interrupts
	bcf	EECON1,WREN	; Clear the EEPROM write enable bit
	incf	EEADR,f		; Increment the EE write address
	return			; Return to the caller 
;
; *******************************************************************************
; *										*
; * Purpose:	Read a byte of EEPROM data at address EEADR into EEDATA.	*
; *										*
; *   Input:	The address EEADR.						*
; *										*
; *  Output:	The value in EEDATA.						*
; *										*
; *    NOTE:	All in BANK 1							*
; *										*
; *******************************************************************************
;
read_EEPROM
;
	bsf	EECON1,RD	; Request the read
	movf	EEDATA,W	; Get the data
	incf	EEADR,f		; Increment the read address
	return			; Return to the caller
;                                                                             
; *******************************************************************************
; *										*
; * Purpose:	Multiply the 32 bit number for oscillator frequency times the	*
; *		32 bit number for the displayed frequency.			*
; *										*
; *   Input:	The reference oscillator value in osc_3 ... osc_0 and the	*
; *		current frequency stored in freq_3 ... freq_0.  The reference	*
; *		oscillator value is treated as a fixed point real, with a 24	*
; *		bit mantissa.							*
; *										*
; *  Output:	The result is stored in AD9851_3 ... AD9851_0.			*
; *										*
; *******************************************************************************
;
calc_dds_word
;
	clrf	AD9851_0	; Clear the AD9850/AD9851 control word bytes
	clrf	AD9851_1	; 
	clrf	AD9851_2	; 
	clrf	AD9851_3	; 
	clrf	AD9851_4	; 
	movlw	0x20		; Set count to 32 (4 osc bytes of 8 bits)
	movwf	mult_count	; Keep running count
	movf	osc_0,w		; Move the four osc bytes
	movwf	osc_temp_0	; to temporary storage for this multiply
	movf	osc_1,w		; (Don't disturb original osc bytes)
	movwf	osc_temp_1	; 
	movf	osc_2,w		; 
	movwf	osc_temp_2	; 
	movf	osc_3,w		; 
	movwf	osc_temp_3	; 
mult_loop
	bcf	STATUS,C	; Start with Carry clear
	btfss	osc_temp_0,0	; Is bit 0 (Least Significant bit) set?
	goto	noAdd		; No, don't need to add freq term to total
	movf	freq_0,w	; Get the "normal" freq_0 term
	addwf	AD9851_1,f	; Add it in to total
	btfss	STATUS,C	; Does this addition result in a carry?
	goto	add7		; No, continue with next freq term
	incfsz	AD9851_2,f	; Yes, add one and check for another carry
	goto	add7		; No, continue with next freq term
	incfsz	AD9851_3,f	; Yes, add one and check for another carry
	goto	add7		; No, continue with next freq term
	incf	AD9851_4,f	; Yes, add one and continue
add7
	movf	freq_1,w	; Get the "normal" freq_0 term
	addwf	AD9851_2,f	; Add freq term to total in correct position
	btfss	STATUS,C	; Does this addition result in a carry?
	goto	add8		; No, continue with next freq term
	incfsz	AD9851_3,f	; Yes, add one and check for another carry
	goto	add8		; No, continue with next freq term
	incf	AD9851_4,f	; Yes, add one and continue
add8
	movf	freq_2,w	; Get the "normal" freq_2 term
	addwf	AD9851_3,f	; Add freq term to total in correct position
	btfss	STATUS,C	; Does this addition result in a carry?
	goto	add9		; No, continue with next freq term
	incf	AD9851_4,f	; Yes, add one and continue
add9
	movf	freq_3,w	; Get the "normal" freq_3 term
	addwf	AD9851_4,f	; Add freq term to total in correct position
noAdd
	rrf	AD9851_4,f	; Shift next multiplier bit into position
	rrf	AD9851_3,f	; Rotate bits to right from byte to byte
	rrf	AD9851_2,f	; 
	rrf	AD9851_1,f	; 
	rrf	AD9851_0,f	; 
	rrf	osc_temp_3,f	; Shift next multiplicand bit into position
	rrf	osc_temp_2,f	; Rotate bits to right from byte to byte
	rrf	osc_temp_1,f	; 
	rrf	osc_temp_0,f	; 
	decfsz	mult_count,f	; One more bit has been done.  Are we done?
	goto	mult_loop	; No, go back to use this bit

#ifdef 	AD9850
	movlw	0x00		; No clock multiplier (AD9850)              
#endif

#ifdef 	AD9851
	movlw	0x01		; Turn on 6x clock multiplier (AD9851)      
#endif

	movwf	AD9851_4	; Last byte to be sent                      
				; Mult answer is in bytes _3 .. _0          
	return			; Done.
;
; *******************************************************************************
; *										*
; * Purpose:	This routine sends the AD9850/AD9851 control word to the DDS	*
; *		using a serial data transfer.					*
; *										*
; *   Input:	AD9851_4 ... AD9851_0						*
; *										*
; *  Output:	The DDS chip register is updated.				*
; *										*
; *******************************************************************************
;
send_dds_word
	movlw	AD9851_0	; Point FSR at Least Significant Byte       
	movwf	FSR		; 
next_byte
	movf	INDF,w		; 
	movwf	byte2send	; 
	movlw	0x08		; Set counter to 8
	movwf	bit_count	; 
next_bit
	rrf	byte2send,f	; Test if next bit is 1 or 0
	btfss	STATUS,C	; Was it zero?
	goto	send0		; Yes, send zero
	bsf	DDS_dat		; No, send one                               
	bsf	DDS_clk		; Toggle write clock                         
	bcf	DDS_clk		;                                            
	goto	break		; 
send0
	bcf	DDS_dat		; Send zero                                  
	bsf	DDS_clk		; Toggle write clock                         
	bcf	DDS_clk		;                                            
break
	decfsz	bit_count,f	; Has the whole byte been sent?
	goto	next_bit	; No, keep going.
	incf	FSR,f		; Start the next byte unless finished
	movlw	AD9851_4+1	; Next byte (past the end)
	subwf	FSR,w		; 
	btfss	STATUS,C	;
	goto	next_byte	;
	bsf	DDS_load	; Send load signal to the AD9850/AD9851             
	bcf	DDS_load	;
	bcf	DDS_dat		;
	bcf	DDS_clk		;
	return			;
;
; *******************************************************************************
; *										*
; * Purpose:	Wait for 1 millisecond.						*
; *										*
; *										*
; *   Input:	None								*
; *										*
; *  Output:	None (NOTE VK5TM Jan 2014 - my calculations make this 1.547mS	*
; *										*
; *******************************************************************************
;
wait_1ms
	movlw	0x2
	movwf	timer1		; 

outer_loop                        
	movlw	0xFF		; Set up inner loop counter
	movwf	timer2		;   to 255
inner_loop
	decfsz	timer2,f	; Decrement inner loop counter
	goto	inner_loop	; If inner loop counter not down to zero, 
				;   then go back to inner loop again
	decfsz	timer1,f	; Yes, Decrement outer loop counter
	goto	outer_loop	; If outer loop counter not down to zero,
				;   then go back to outer loop again
	return			; Yes, return to caller
;
        END
;------------------------------------------------------------------------------------------------